package com.guoyihao.codesandbox.codesandbox;

import cn.hutool.core.io.FileUtil;
import com.guoyihao.codesandbox.model.ExecuteCodeRequest;
import com.guoyihao.codesandbox.model.ExecuteCodeResponse;
import com.guoyihao.codesandbox.model.ExecuteMessage;
import com.guoyihao.codesandbox.model.JudgeInfo;
import com.guoyihao.codesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 实现C++代码编译复用的抽象类
 */
@Slf4j
public abstract class CppCodeSandBoxTemplate implements CodeSandBox{
    public static final String GLOBAL_CODE_DIR_NAME = "tmpCode";
    public static final String GLOBAL_CPP_CLASS_NAME = "Main.cpp";
    public static final String MAIN_CLASS = "Main";
    public static final Long TIME_LIMIT = 1000L;

    /**
     * 1.把用户的代码保存为文件
     * @param code 用户代码
     * @return
     */
    public File saveCodeToFile(String code){
        //获取当前工作目录
        String userDir = System.getProperty("user.dir");
        //得到tmpCode路径
        String globalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;
        //把用户的代码隔离存放
        String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();

        String userCodePath = userCodeParentPath + File.separator + GLOBAL_CPP_CLASS_NAME;
        //将代码写入到文件中
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

        log.info("用户代码位置：" + userCodeFile);
        return userCodeFile;
    }

    /**
     * 2.编译代码，得到 class 文件
     * @param absolutePath class文件的路径
     * @return
     */
    public ExecuteMessage compileCode(String absolutePath){
        ExecuteMessage executeMessage = null;
        //要获取源文件即cpp文件以及编译过后的文件路径
        String sourceFilePath = absolutePath + File.separator + GLOBAL_CPP_CLASS_NAME;
        String outputFilePath = absolutePath + File.separator + MAIN_CLASS;
        String compileCmd = String.format("g++ -o %s %s -std=c++11", outputFilePath, sourceFilePath);
        log.info("编译指令：{}", compileCmd);
        try {
            //执行编译命令
            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
            executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
            log.info(executeMessage.toString());
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        return executeMessage;
    }

    /**
     * 3. 运行用户的代码，附带输入用例作为参数输入
     * @param inputs
     * @param userCodeParentPath
     * @return
     */
    public List<ExecuteMessage> executeCode(List<String> inputs, String userCodeParentPath) {
        List<ExecuteMessage> executeMessageList = new ArrayList<>();
        String executablePath = userCodeParentPath + File.separator + MAIN_CLASS; // 修正可执行文件路径
        for (String inputArgs : inputs) {
            try {
                //String execCmd = String.format("ulimit -v 256000; %s %s", userCodeParentPath, CLASS_NAME);
                // 构造完整的 Shell 命令
                String[] cmdArray = {
                        "/bin/bash",
                        "-c",
                        String.format("ulimit -v 256000; %s", executablePath)
                };

                log.info("execCmd：{}", cmdArray);
                Process runProcess = Runtime.getRuntime().exec(cmdArray);
                // **通过标准输入流写入输入数据**

                // 超时控制
                new Thread(() -> {
                    try {
                        Thread.sleep(TIME_LIMIT);
                        System.out.println("超时中断");
                        runProcess.destroy();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }).start();
                ExecuteMessage executeMessage = ProcessUtils.runInteractProcessAndGetMessage(runProcess, inputArgs);
                executeMessageList.add(executeMessage);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return executeMessageList;
    }

    /**
     * 封装返回值
     * @param executeMessageList
     * @return
     */
    public ExecuteCodeResponse processResponse(List<ExecuteMessage> executeMessageList) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setStatus(0);
        long time = 0L;
        List<String> outputList = new ArrayList<>();
        for(ExecuteMessage executeMessage : executeMessageList){
            //返回值有不正常的
            if(StringUtils.isNotBlank(executeMessage.getErrorMessage())){
                executeCodeResponse.setStatus(1);
                executeCodeResponse.setMessage(executeMessage.getErrorMessage());
                break;
            }
            //运行时间
            time = Math.max(time,executeMessage.getTime());
            outputList.add(executeMessage.getMessage());
        }
        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(time);
        executeCodeResponse.setOutputList(outputList);
        executeCodeResponse.setJudgeInfo(judgeInfo);
        return executeCodeResponse;
    }

    /**
     * 5. 关闭资源
     * @param userCodeFile
     * @param userCodeParentPath
     * @return
     */
    public boolean closeResource(File userCodeFile, String userCodeParentPath) {
        if (userCodeFile.getParentFile() != null) {
            boolean del = FileUtil.del(userCodeParentPath);
            log.info("删除{}", (del ? "成功" : "失败"));
            return del;
        }
        return true;
    }

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        //1.将代码写入到文件中
        String code = executeCodeRequest.getCode();
        File userCodeFile = this.saveCodeToFile(code);

        //2.编译代码
        ExecuteMessage executeMessage = compileCode(userCodeFile.getParentFile().getAbsolutePath());

        //3.运行代码
        List<String> inputs = executeCodeRequest.getInputList();
        List<ExecuteMessage> executeMessageList = executeCode(inputs, userCodeFile.getParentFile().getAbsolutePath());

        //4.封装数据
        ExecuteCodeResponse executeCodeResponse = processResponse(executeMessageList);

        //5.关闭资源
        boolean b = closeResource(userCodeFile, userCodeFile.getParentFile().getAbsolutePath());
        if (!b)
            throw new RuntimeException("关闭资源错误");
        return executeCodeResponse;
    }
}
